package com.baoteng.rbspy.util;

import static java.util.Locale.ENGLISH;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baoteng.rbspy.annotation.TransferProperty;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

public class BeanConvertUtil {

	private final Logger logger = LoggerFactory.getLogger(BeanConvertUtil.class);
	
	private static ConcurrentHashMap<String, List<BeanMethod>> beanMethodMap = new ConcurrentHashMap<String, List<BeanMethod>>();

	public static int getRandomAge(Date birthday){
		long now = System.currentTimeMillis();
		return (int)((now - birthday.getTime())/365*24*60*60*1000);
	}
	
	public static Date getRandomBirthday(){
		long date = System.currentTimeMillis();
		try {
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");  
			Date startDate = format.parse("1950-01-01");
			Date endDate = format.parse("2000-01-01");
			date = startDate.getTime() + (long)(Math.random()*(startDate.getTime()- endDate.getTime()));
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		return new Date(date);
	}
	 private static final List<String> formarts = new ArrayList<String>(4);
     static{
         formarts.add("yyyy-MM");
         formarts.add("yyyy-MM-dd");
         formarts.add("yyyy-MM-dd hh:mm");
         formarts.add("yyyy-MM-dd hh:mm:ss");
     }

	// 读多写少，不加锁
	// private static final Object lock = new Object();

	private BeanConvertUtil() {
	}

	private static class BeanConvertUtilHolder {
		private static final BeanConvertUtil handler = new BeanConvertUtil();
	}

	public static BeanConvertUtil newInstance() {
		return BeanConvertUtilHolder.handler;
	}

	private boolean compatible(Class<?> getter, Class<?> setter) {
		return setter.isAssignableFrom(getter);
	}

	public static String getOrderIdByUUId() {
        int machineId = 1;//最大支持1-9个集群机器部署
        int hashCodeV = UUID.randomUUID().toString().hashCode();
        if(hashCodeV < 0) {//有可能是负数
            hashCodeV = - hashCodeV;
        }
        // 0 代表前面补充0     
        // 4 代表长度为4     
        // d 代表参数为正数型
        return machineId + String.format("%015d", hashCodeV);
    }
	
	/**
	 * 把bean转换成http请求参数
	 * @param from
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public String convert2RequestURLParam(Object from){
		Preconditions.checkNotNull(from, "convert2RequestURLParam fail.from argument must not be null.");
		Class fromClazz = from.getClass();
		List<BeanMethod> beans = beanMethodMap.get(fromClazz.toString());
		StringBuilder urlParam = new StringBuilder();
		if(beans == null){
			beans = Lists.newArrayList();
			Field[] getters = ArrayUtils.addAll(fromClazz.getDeclaredFields(), fromClazz.getSuperclass().getDeclaredFields());//ReflectUtils.getBeanProperties(fromClazz);Map names = new HashMap();
			
			for (Field getter : getters) {
				TransferProperty property = getter.getAnnotation(TransferProperty.class);
				if (property != null) {
					if(property.ignore()) continue;
				}
				try {
					MethodHandles.Lookup lookup = MethodHandles.lookup();
					
					MethodHandle read =  lookup.findVirtual(from.getClass(), getBeanMethod(getter.getName(),"get",getter.getType()), MethodType.methodType(getter.getType(), new Class[]{}));//lookup.unreflectGetter(getter);//(fromClazz, getter.getName(), getter.getType());
					BeanMethod bean = new BeanMethod();
					bean.setReader(read);
					bean.setGetter(getter);
					
					beans.add(bean);
					String param = invokeParam(from , bean);
					urlParam.append(param!=null?param:"");
				} catch (NoSuchMethodException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			if (beans.size() > 0) {
				beanMethodMap.put(fromClazz.toString(), beans);
			}
		}else{
			for (BeanMethod bean : beans) {
				try {
					String param = invokeParam(from , bean);
					urlParam.append(param!=null?param:"");
				} catch (Throwable e) {
					e.printStackTrace();
				}
			}
		}
		
		if(urlParam!=null && urlParam.length()>0){
			if(urlParam.substring(urlParam.length() - 1).equals("&")){
				return urlParam.substring(0,urlParam.length() - 1);
			}else{
				return urlParam.toString();
			}
		}else{
			return null;
		}
		
	}
	
	private String invokeParam(Object from,BeanMethod method){
		StringBuilder param = new StringBuilder();
		try {
			Object read = method.getReader().invoke(from);
			//"",null不转换，
			if (read != null) {
				TransferProperty property = method.getGetter().getAnnotation(TransferProperty.class);
				if (property != null) {
					if(!property.ignore()) {
						param.append(property.value());
						method.setDateFormat(property.dateFormat());
					}else{
						return null;
					}
				} else {
					param.append(method.getGetter().getName());
				}
				param.append("=");
				if(read instanceof String){
					/**
					 * urlencode为GBK编码
					 */
					param.append(URLEncoder.encode((String)read,"GBK"));
				}else if(read instanceof java.util.Date){
					logger.info("date {} --> string {}",method.getReader().toString());
					param.append(StringUtils.isNotBlank(method.getDateFormat())?DateFormatUtils.format((Date) read, method.getDateFormat()):DateFormatUtils.format((Date) read, "yyyy-MM-dd HH:mm:ss"));
				}else if(read instanceof java.time.LocalDate){
					logger.info("LocalDate {} --> string {}",method.getReader().toString());
					LocalDate date = (LocalDate) read;
					param.append(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
				}else if(read instanceof java.time.LocalDateTime){
					logger.info("LocalDateTime {} --> string {}",method.getReader().toString());
					LocalDateTime date = (LocalDateTime) read;
					param.append(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
				}else {
					param.append(read.toString());
				}
				param.append("&");
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return param.toString();
	}
	
	private String toUpperForFirstLetter(String propertyName){
		return StringUtils.isNotBlank(propertyName) ? new StringBuilder()
				.append(propertyName.substring(0, 1).toUpperCase()).append(propertyName.substring(1)).toString() : null;
	}
	
	/**
	 * bean转换，只对实现get set方法的属性进行转换，如果类型不一致不进行转换
	 * 
	 * @param from
	 * @param to
	 * @return
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object convert(Object from, Object to) {

		Preconditions.checkNotNull(from, "bean convert fail.from argument must not be null.");
		Preconditions.checkNotNull(to, "bean convert fail.to argument must not be null.");

		Class fromClazz = from.getClass();
		Class toClazz = to.getClass();
		String classKey = generateKey(fromClazz, toClazz);
		List<BeanMethod> beans = beanMethodMap.get(classKey);
		if (beans == null) {
			beans = Lists.newArrayList();
			Field[] getters = ArrayUtils.addAll(fromClazz.getDeclaredFields(), fromClazz.getSuperclass().getDeclaredFields());//ReflectUtils.getBeanProperties(fromClazz);
			Field[] setters = ArrayUtils.addAll(toClazz.getDeclaredFields(), toClazz.getSuperclass().getDeclaredFields());//ReflectUtils.getBeanSetters(toClazz);//
			Map names = new HashMap();
			for (Field getter : getters) {
				TransferProperty property = getter.getAnnotation(TransferProperty.class);
				if (property != null ) {
					if(!property.ignore()) names.put(property.value(), getter);
				} else {
					names.put(getter.getName(), getter);
				}
			}
			for (Field setter : setters ) {
				// 无注解默认不进行类型检测，强转
				boolean isCast = true;
				String dateFormat = null;
				TransferProperty property = setter.getAnnotation(TransferProperty.class);
				String setterName = setter.getName();
				if (property != null) {
					setterName = property.value();
					isCast = property.isCast();
					dateFormat = property.dateFormat();
					if(property.ignore()) continue;
				}
				Field getter = (Field) names.get(setterName);
				if (getter != null) {
					boolean isSame = this.compatible(getter.getType(), setter.getType());

					if (!isSame && !isCast && dateFormat == null)
						continue;

					try {
						MethodHandles.Lookup lookup = MethodHandles.lookup();
						
						MethodHandle read =  lookup.findVirtual(from.getClass(), getBeanMethod(getter.getName(),"get",getter.getType()), MethodType.methodType(getter.getType(), new Class[]{}));//lookup.unreflectGetter(getter);//(fromClazz, getter.getName(), getter.getType());
						MethodHandle write = lookup.findVirtual(to.getClass(), getBeanMethod(setter.getName(),"set",setter.getType()), MethodType.methodType(void.class,setter.getType()));// lookup.findSetter(setter.getClass(), setter.getName(), setter.getType());
						BeanMethod bean = new BeanMethod();
						bean.setReader(read);
						bean.setWriter(write);
						bean.setGetter(getter);
						bean.setSetter(setter);
						bean.setDateFormat(dateFormat);

						beans.add(bean);
						invoke(from, to, bean);

					} catch (Throwable e) {
						e.printStackTrace();
					}
				}
			}
			if (beans.size() > 0) {
				beanMethodMap.put(classKey, beans);
			}
		} else {
			for (BeanMethod bean : beans) {
				try {
					invoke(from, to, bean);
				} catch (Throwable e) {
					e.printStackTrace();
				}
			}
		}
		return to;
	}

	private String getBeanMethod(String methodName, String prefix,Class<?> propertyType) {
		if ("get".equals(prefix) && propertyType == boolean.class || propertyType == null) {
            return new StringBuilder("is").append(methodName.substring(0, 1).toUpperCase(ENGLISH)).append(methodName.substring(1)).toString();
		}else{
			return new StringBuilder(prefix).append( methodName.substring(0, 1).toUpperCase(ENGLISH)).append(methodName.substring(1)).toString();
		}
	}
	
	private void invoke(Object from, Object to, BeanMethod method) {
		try {
			Object read = method.getReader().invoke(from);
			//"",null不转换，
			if (read != null) {
				if(read instanceof String){
					//空串不转换
					if(StringUtils.isBlank((String)read)){
						return ;
					}
				}
				if (StringUtils.isNotBlank(method.getDateFormat())) {
					Class<?> readClazz = method.getGetter().getType();
					Class<?> writeClazz = method.getSetter().getType();
					if (compatible(readClazz, java.util.Date.class)
							&& compatible(writeClazz, String.class)) {
						logger.info("date {} --> string {}",method.getReader().toString(),method.getWriter().toString());
						method.getWriter().invoke(to, DateFormatUtils.format((Date) read, method.getDateFormat()));
					} else if (compatible(readClazz, String.class)
							&& compatible(writeClazz, java.util.Date.class)) {
						logger.info("string {} --> date {}",method.getReader().toString(),method.getWriter().toString());
						method.getWriter().invoke(to, org.apache.commons.lang3.time.DateUtils.parseDate((String) read,
								method.getDateFormat()));
					}else{
						method.getWriter().invoke(to, read);
					}
				} else {
					method.getWriter().invoke(to, read);
				}
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

	private String generateKey(Class<?> fromClazz, Class<?> toClazz) {
		return fromClazz.toString() + toClazz.toString();
	}
	
	
}
